package com.mlh.skill;

import java.util.Arrays;

/**
 * @author 缪林辉
 * @date 2024/7/5 10:16
 * @DESCRIPTION
 */
//整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。
// 例如，arr = [1,2,3] ，以下这些都可以视作 arr 的排列：[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
// 整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地，如果数组的所有排列根据其字典顺序从小到大排列在一个容器中，
// 那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列，那么这个数组必须重排为字典序最小的排列（即，其元素按升序排列）。
// 例如，arr = [1,2,3] 的下一个排列是 [1,3,2] 。
// 类似地，arr = [2,3,1] 的下一个排列是 [3,1,2] 。
// 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ，因为 [3,2,1] 不存在一个字典序更大的排列。

//************题目简述
//给定一个数组里面有若干个数字，将其组合为一个整数。
// 如何将这些数字重新排列，以得到下一个更大的整数。如 123 下一个更大的数为 132。如果没有更大的整数，则输出最小的整数。

// 给你一个整数数组 nums ，找出 nums 的下一个排列。
// 必须 原地 修改，只允许使用额外常数空间。
public class 下一个排列 {

    //前置知识：所有数字 凑出最大的数 就是这些数的升序排列

    //力扣
    //算法推导
    //首先分析一个数变大过程的思想
    // 如何得到这样的排列顺序？这是本文的重点。我们可以这样来分析：
    // 我们希望下一个数 比当前数大，这样才满足 “下一个排列” 的定义。
    // 因此只需要 将后面的「大数」与前面的「小数」交换，就能得到一个更大的数。
    // 比如 123456，将 5 和 6 交换就能得到一个更大的数 123465。
    // 我们还希望下一个数 增加的幅度尽可能的小，这样才满足“下一个排列与当前排列紧邻“的要求。
    // 为了满足这个要求，我们需要：在 尽可能靠右的低位 进行交换，需要 从后向前 查找
    // 将一个 尽可能小的「大数」 与前面的「小数」交换。
    // 比如 123465，下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换
    // 将「大数」换到前面后，需要将「大数」后面的所有数 重置为升序，升序排列就是最小的排列。
    // 以 123465 为例：首先按照上一步，交换 5 和 4，得到 123564；
    // 然后需要将 5 之后的数重置为升序，得到 123546。
    // 显然 123546 比 123564 更小，123546 就是 123465 的下一个排列
    // 以上就是求 “下一个排列” 的分析过程。

    public void method1(int[] nums) {
        int len=nums.length;
        int cIndex=-1;
        for (int i = len-2; i >=0; i--) {
            if(cIndex!=-1){
                break;
            }
            for (int j = len-1; j >i; j--) {//从后向前遍历
                if(nums[i]<nums[j]){//找到第一个大于nums[i]的数
                    //交换
                    int temp=nums[j];
                    nums[j]=nums[i];
                    nums[i]=temp;
                    cIndex=i;
                    break;
                }
            }
        }
        // if(cIndex==-1){
        //     //对cIndex后面的进行有序排序
        //     Arrays.sort(nums);
        // }else{
        //     //对cIndex后面的进行有序排序
        //     Arrays.sort(nums,cIndex+1,len-1);
        // }
        reverse(nums,cIndex+1);
    }

    //第二种解法  比第一中解法少了排序，为什么能少排序和他的步骤有关

    // 具体地，我们这样描述该算法，对于长度为 n 的排列 a：
    // 首先从后向前查找第一个顺序对 (i,i+1)，满足 a[i]<a[i+1]。这样「较小数」即为 a[i]。此时 [i+1,n) 必然是下降序列。
    // 如果找到了顺序对，那么在区间 [i+1,n) 中从后向前查找第一个元素 j 满足 a[i]<a[j]。这样「较大数」即为 a[j]。
    // 交换 a[i] 与 a[j]，此时可以证明区间 [i+1,n) 必为降序。我们可以直接使用双指针反转区间 [i+1,n) 使其变为升序，而无需对该区间进行排序。

    // 第一种解法和第二种解法其实是相同的，都是用两边扫描的方式找到 index然后从尾巴往前找到第一个大于nums[index]的元素
    // 然后交换之后，对index对后面的元素进行逆序

    
    // 练习 把第二个解法实现一下
    public void practice(int[] nums) {
        int len=nums.length;
        // 从后往前寻找第一个前比后小的位置索引
        int index=-1;
        for (int i = len-1; i >0; --i) {
            if(nums[i]>nums[i-1]){
                index=i-1;
                break;
            }
        }
        // index=-1说明没进入循环，此时整个数组都是降序的
        if(index!=-1){
            for (int i = len-1; i >index ; --i) {
                // 找到index后面从后到前第一个大于index的元素，然后交换位置
                if(nums[i]>nums[index]){
                    nums[i]=nums[i]^nums[index];
                    nums[index]=nums[i]^nums[index];
                    nums[i]=nums[i]^nums[index];
                    break;
                }
            }
        }
        reverse(nums,index+1);
    }

    // 对start开始的元素进行逆序
    public void reverse(int[]nums,int start){
        int end=nums.length-1;
        while(start<end){
            nums[start]=nums[start]^nums[end];
            nums[end]=nums[start]^nums[end];
            nums[start]=nums[start]^nums[end];
            ++start;
            --end;
        }
    }
}
